﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using log4net.Layout;
using log4net.Appender;
using log4net.Repository.Hierarchy;
using log4net;
using log4net.Core;
using log4net.Config;

namespace DSQTransferService.Public
{
    public class LogHelper
    {
        #region 单例模式
        private static readonly LogHelper instance = null;
        static LogHelper()
        {
            instance = new LogHelper();
        }
        internal LogHelper()
        {
            Thread threadWriteInfoLog = new Thread(WriteInfoLogThreadOnTiming);
            threadWriteInfoLog.IsBackground = true;
            threadWriteInfoLog.Priority = ThreadPriority.Lowest;
            threadWriteInfoLog.Start();

            TempExpInfo.Add(LogModelType.Alarm, "");
            TempExpInfo.Add(LogModelType.BINDINGSCANNER, "");
            TempExpInfo.Add(LogModelType.BLL, "");
            TempExpInfo.Add(LogModelType.CAMERA, "");
            TempExpInfo.Add(LogModelType.CAR, "");
            TempExpInfo.Add(LogModelType.CHUTE, "");
            TempExpInfo.Add(LogModelType.DB, "");
            TempExpInfo.Add(LogModelType.ELECTRICENERGY, "");
            TempExpInfo.Add(LogModelType.FEEDER, "");
            TempExpInfo.Add(LogModelType.GRAYCAMERA, "");
            TempExpInfo.Add(LogModelType.MANUAL, "");
            TempExpInfo.Add(LogModelType.OPERATION, "");
            TempExpInfo.Add(LogModelType.PANORAMICCAMERA, "");
            TempExpInfo.Add(LogModelType.PLC, "");
            TempExpInfo.Add(LogModelType.Scada, "");
            TempExpInfo.Add(LogModelType.StandardScada, "");
            TempExpInfo.Add(LogModelType.TRACK, "");
            TempExpInfo.Add(LogModelType.TRANSBELT, "");
            TempExpInfo.Add(LogModelType.VOLUMETER, "");
            TempExpInfo.Add(LogModelType.WCS, "");


        }
        public static LogHelper Instance
        {
            get
            {
                return instance;
            }
        }
        #endregion

        #region enum
        /// <summary>
        /// 日志等级
        /// </summary>
        public enum LogLevel
        {
            Operation,
            Warning,
            Info,
            Exception
        }
        #endregion

        #region defined
        const string log4DirName = "WCSLog";
        const string logModuleType_main = "MainLog";//默认的主模块类型
        List<string> LogTyp = new List<string>() { "Info", "RTI", "Exception", "Operation", "Warning" };//日志类型
        List<string> LogModuleType = new List<string>() { };//日志模块类型列表，由外部自定义
        Dictionary<string, string> infoBufferDic = new Dictionary<string, string>();//存储信息的字典缓冲区
        /// <summary>
        /// 上句异常日志缓存
        /// </summary>
        Dictionary<string, string> TempExpInfo = new Dictionary<string, string>();

        object logLockObj = new object();//获取日志实例的对象锁
        object objInfoDic = new object();

        bool writingLog = false;//监测是否正在写入日志
        /// <summary>
        /// 日志缓存
        /// </summary>
        Queue<LogItem> LogBuffer = new Queue<LogItem>();
        #region attribute
        private int intervalTime = 5000;

        /// <summary>
        /// 设置定时写日志的时间间隔,默认5000,单位:毫秒
        /// </summary>
        public int IntervalTime
        {
            get { return intervalTime; }
            set
            {
                if (value < 500) intervalTime = 1000;
                else intervalTime = value;
            }
        }

        /// <summary>
        /// 日志结构体
        /// </summary>
        public struct LogItem
        {
            public DateTime Time;
            public string ModelName;
            public string Type;
            public string Content;

            public LogItem(DateTime time, string modelName, string type, string content)
            {
                Time = time;
                ModelName = modelName;
                Type = type;
                Content = content;
            }
        }
        #endregion
        #endregion

        /// <summary>
        /// event传出消息
        /// </summary>
        public EventHandler<string> HintEventHandler;

        /// <summary>
        /// 日志模块类型，为考虑不修改其他模块代码目前调用，暂时保留
        /// </summary>
        public struct LogModelType
        {
            public const string BLL = "BLLLog";
            public const string Scada = "ScadaLog";
            public const string WCS = "MainLog";
            public const string CAR = "CarLog";
            public const string CHUTE = "ChuteLog";
            public const string CAMERA = "CameraLog";
            public const string FEEDER = "FeederLog";
            public const string GRAYCAMERA = "GrayCameraLog";
            public const string MANUAL = "ManualLog";
            public const string TRACK = "TrackLog";
            public const string PLC = "PLCLog";
            public const string DB = "DBLog";
            public const string OPERATION = "Operation";
            public const string TRANSBELT = "TransBelt";
            public const string PANORAMICCAMERA = "PanoramicCamera";
            public const string StandardScada = "StandardScada";
            public const string BINDINGSCANNER = "BindingScanner";
            public const string ELECTRICENERGY = "ElectricEnergy";
            public const string VOLUMETER = "Volumeter";
            public const string Alarm = "Alarm";
            public const string STOEquipment = "STOEquipment";
            public const string StorageLoad = "StorageLoad";
            public const string Config = "ConfigLog";
            public const string Utilities = "Utilities";
            public const string ReaderPlatform = " ReaderPlatform";
            public const string OPCUA = " OPCUA";
        }


        #region public methods
        ZipUtility zipUtility = new ZipUtility();
        /// <summary>
        ///删除文件夹
        /// </summary>
        /// <param name="mainPath">主文件夹名称，例如：WCSLog</param>
        /// <param name="dt">最大保留到哪天</param>
        /// <returns></returns>
        public void DeleteLog(DateTime dt, string mainPath = log4DirName)
        {
            if (!Directory.Exists(mainPath))
            {
                Info(string.Format("不存在该日志目录,不进行删除" + mainPath));
                LogHelper.Instance.Info(string.Format("不存在该日志目录,不进行删除" + mainPath));
                return;
            }
            try
            {
                string maxFileName = dt.ToString("yyyyMMdd");
                string[] allFileNames = Directory.GetDirectories(mainPath);//目录不存在，会抛异常
                string exePath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
                Info(string.Format("准备删除日志，目录数量：{0}", allFileNames.Count()));
                DeleteZipFile(maxFileName);
                for (int i = 0; i < allFileNames.Count(); i++)
                {
                    string[] str = allFileNames[i].Split('\\');

                    Info($"str[0]:{str[0]};str[1]:{str[1]};");
                    if (str[1].Length < 8) continue;
                    string currentFileName = str[1].Substring(0, 8);

                    int currentFileNameDt = 0;
                    int.TryParse(currentFileName, out currentFileNameDt);
                    if (currentFileNameDt == 0) continue;
                    if (int.Parse(currentFileName) < int.Parse(maxFileName))
                    {
                        Info(string.Format("准备删除日志，目录名：{0} 保留最早目录:{1}", currentFileName, maxFileName));
                        string deleteFilePath = exePath + allFileNames[i];
                        DirectoryInfo di = new DirectoryInfo(deleteFilePath);
                        foreach (FileInfo file in di.EnumerateFiles())
                        {
                            try
                            {
                                file.Delete();
                            }
                            catch (Exception ex)
                            {
                                Info($"删除文件:{file.Name}出错." + ex.ToString());
                            }
                        }
                        try
                        {
                            di.Delete(true);
                        }
                        catch (Exception ex) { Info($"删除文件夹:{di.Name}出错." + ex.ToString()); }
                        LogHelper.Instance.Info(string.Format("删除了文件夹，目录：{0}", currentFileName));
                        Info(string.Format("删除了文件夹，目录：{0}", currentFileName));
                    }
                    else
                    {
                        string rootPath = exePath + allFileNames[i];
                        if (int.Parse(currentFileName) == int.Parse(DateTime.Now.ToString("yyyyMMdd")))
                        {
                            Info(string.Format("当天的日志不删除，目录名：{0} 保留最早目录:{1}", currentFileName, maxFileName));
                        }
                        else
                        {
                            if (zipUtility.ZipFileFromDirectory(rootPath, exePath + "\\" + log4DirName + "\\" + currentFileName + "Zip.zip", 9))
                            {
                                Info(string.Format("准备删除日志，目录名：{0} 保留最早目录:{1}", currentFileName, maxFileName));
                                string deleteFilePath = exePath + allFileNames[i];
                                DirectoryInfo di = new DirectoryInfo(deleteFilePath);
                                foreach (FileInfo file in di.EnumerateFiles())
                                {
                                    try
                                    {
                                        file.Delete();
                                    }
                                    catch (Exception ex)
                                    {
                                        Info($"删除文件:{file.Name}出错." + ex.ToString());
                                    }
                                }
                                try
                                {
                                    di.Delete(true);
                                }
                                catch (Exception ex) { Info($"删除文件夹:{di.Name}出错." + ex.ToString()); }
                                LogHelper.Instance.Info(string.Format("删除了文件夹，目录：{0}", currentFileName));
                                Info(string.Format("删除了文件夹，目录：{0}", currentFileName));
                            }
                        }
                        //Info(string.Format("不删除日志，目录名：{0} 保留最早目录:{1}", currentFileName, maxFileName));
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.Instance.Info(string.Format("LogHelper.DeleteLog->{0}", ex.ToString()));

                Exception(string.Format("删除日志出错，目录：{0}", ex.ToString()));
                //throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// 删除多余的压缩文件
        /// </summary>
        /// <param name="maxFileName"></param>
        public void DeleteZipFile(string maxFileName)
        {
            try
            {
                string[] allFileNames = Directory.GetFiles(log4DirName);//目录不存在，会抛异常
                string exePath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
                for (int i = 0; i < allFileNames.Count(); i++)
                {
                    string[] str = allFileNames[i].Split('\\');
                    if (!str[1].Contains("Zip"))
                    {
                        continue;
                    }
                    //string currentFileName = str[1].Replace("Zip", "");//需要把解压的文件也删除 解压之后的名称：20231010Zip(2)
                    string currentFileName = str[1].Substring(0, 8);//取出日期字段
                    Info($"allFileNames[i]:{allFileNames[i]};currentFileName:{currentFileName};maxFileName:{maxFileName}");
                    if (int.Parse(currentFileName) < int.Parse(maxFileName))
                    {
                        Info($"存在{exePath + allFileNames[i]}文件");
                        if (File.Exists(exePath + allFileNames[i]))
                        {
                            try
                            {
                                File.Delete(exePath + allFileNames[i]);
                            }
                            catch (Exception ex)
                            {
                                Info($"删除文件:{exePath + allFileNames[i]}出错." + ex.ToString());
                            }
                        }
                        else
                        {
                            Info($"不存在{exePath + allFileNames[i]}文件");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                instance.Exception($"DeleteZipFile has error:{ex.ToString()}");
            }
        }

        /// <summary>
        /// 记录正常流程信息,默认存入主日志MainLog
        /// </summary>
        /// <param name="content"></param>
        /// <param name="logModelName"></param>
        public void Info(string content, string logModelName = logModuleType_main)
        {
            if (LogModelType.Scada == logModelName)
                return;
            AddMsgToBuffer(LogTyp[0], logModelName, content);
        }

        /// <summary>
        /// 记录高实时性（定时）信息（比如消息高频定时发送）,默认存入主日志MainLog
        /// </summary>
        /// <param name="content"></param>
        /// <param name="logModelName"></param>
        public void RealtimeInfo(string content, string logModelName = logModuleType_main)
        {
            AddMsgToBuffer(LogTyp[1], logModelName, content);
        }

        /// <summary>
        /// 记录异常信息,默认存入主日志MainLog
        /// </summary>
        /// <param name="content"></param>
        /// <param name="logModelName"></param>
        public void Exception(string content, string logModelName = logModuleType_main)
        {
            if (TempExpInfo.ContainsKey(logModelName))
            {
                if (TempExpInfo[logModelName] == content)
                {
                    return;
                }
                else
                {

                    AddMsgToBuffer(LogTyp[2], logModelName, TempExpInfo[logModelName]);
                    AddMsgToBuffer(LogTyp[2], logModelName, content);
                    TempExpInfo[logModelName] = content;

                }
            }
            else
            {

                AddMsgToBuffer(LogTyp[2], logModelName, content);
            }

        }

        /// <summary>
        /// 记录操作信息,默认存入主日志MainLog
        /// </summary>
        /// <param name="content"></param>
        /// <param name="logModelName"></param>
        public void Operation(string content, string logModelName = logModuleType_main)
        {
            AddMsgToBuffer(LogTyp[3], logModelName, content);
        }

        /// <summary>
        /// 记录操作信息,默认存入主日志MainLog
        /// </summary>
        /// <param name="content"></param>
        /// <param name="logModelName"></param>
        public void Warning(string content, string logModelName = logModuleType_main)
        {
            AddMsgToBuffer(LogTyp[4], logModelName, content);
        }
        #endregion

        #region private methods

        /// <summary>
        /// 添加数据到缓冲区
        /// </summary>
        /// <param name="_logType"></param>
        /// <param name="logModelName"></param>
        /// <param name="content"></param>
        private void AddMsgToBuffer(string _logType, string logModelName, string newMsg)
        {
            try
            {
                DateTime dtNow = DateTime.Now;
                lock (objInfoDic)
                {
                    LogBuffer.Enqueue(new LogItem(DateTime.Now, logModelName, _logType, newMsg));
                }
            }
            catch (Exception ex)
            {
                //LogHelper.Instance.Info(string.Format("LogHelper.AddMsgToBuffer->:{0}", ex.ToString()));
            }
        }

        /// <summary>
        /// 定时写日志线程
        /// </summary>
        private void WriteInfoLogThreadOnTiming()
        {
            Instance.Info($"线程方法名:LohHelper->WriteInfoLogThreadOnTiming;线程id:{AppDomain.GetCurrentThreadId()}");
            while (true)
            {
                try
                {
                    if (writingLog)
                        return;
                    writingLog = true;

                    //********** 旧方式 **********
                    //int count = LogBuffer.Count;
                    //for (int i = 0; i < count; i++)
                    //{
                    //    LogItem logItem = LogBuffer.Dequeue();
                    //    string content = string.Format("[{0}] {1} ", logItem.Time.ToString("yyyy-MM-dd HH:mm:ss,fff"), logItem.Content);  //加入实时时间
                    //    WriteLog(logItem.ModelName, logItem.Type, content);
                    //}

                    //********** 新方式 **********
                    int count = LogBuffer.Count;
                    if (count != 0)
                    {
                        List<LogItem> lstLogItem = new List<LogItem>();
                        //加锁取数据
                        lock (objInfoDic)
                        {
                            for (int i = 0; i < count; i++)
                            {
                                LogItem logItem = LogBuffer.Dequeue();
                                lstLogItem.Add(logItem);
                            }
                        }
                        //开始写数据
                        for (int i = 0; i < count; i++)
                        {
                            if (lstLogItem[i].Content != null && lstLogItem[i].Content != string.Empty)
                            {
                                string content = string.Format("[{0}] {1} ", lstLogItem[i].Time.ToString("yyyy-MM-dd HH:mm:ss,fff"),
                                    lstLogItem[i].Content);  //加入实时时间
                                WriteLog(lstLogItem[i].ModelName, lstLogItem[i].Type, content);
                            }
                        }
                    }

                    writingLog = false;
                }
                catch (Exception ex)
                {
                    LogHelper.Instance.Info(ex.Message);
                    writingLog = false;
                }
                Thread.Sleep(intervalTime);
            }
        }

        /// <summary>
        /// 写日志
        /// </summary>
        /// <param name="logModelName">日志模型名称，每个模块的名称</param>
        /// <param name="logType">日志类型【exception、info...】</param>
        /// <param name="content">日志内容</param>
        private void WriteLog(string logModelName, string logType, string content)
        {
            try
            {
                string logDirName = string.Format("{0}", logType);
                string logName = string.Format("{0}_{1}_{2}", logModelName, logType, DateTime.Now.ToString("yyyyMMdd"));
                ILog log = GetLoggerByName(logDirName, logName);

                log.Info(content);
            }
            catch (Exception ee)
            {
                LogHelper.Instance.Info(ee.Message);
            }
        }


        /// <summary>
        /// 获取log实例
        /// </summary>
        /// <param name="logDirName">log文件夹名称</param>
        /// <param name="logName">log文件名称</param>
        /// <returns></returns>
        private ILog GetLoggerByName(string logDirName, string logName)
        {
            string name = $"{log4DirName}\\{DateTime.Now.ToString("yyyyMMdd")}\\{logDirName}\\{logName}.log";
            /*加锁判断，防止多线程访问，命名初始化多次而数据重复输出*/
            if (log4net.LogManager.Exists(name) == null)
            {
                lock (logLockObj)
                {
                    if (log4net.LogManager.Exists(name) == null)
                    {

                        InitLoggerByName(name);
                    }
                }
            }

            var log = LogManager.GetLogger(name);
            return log;
        }

        /// <summary>
        /// 初始化日志文件
        /// </summary>
        /// <param name="name"></param>
        private void InitLoggerByName(string name)
        {
            #region 初始化日志文件
            // Pattern Layout defined
            PatternLayout patternLayout = new PatternLayout();
            //输出信息的模式,
            //patternLayout.ConversionPattern = "%date %thread %level %logger - %message%newline";
            //patternLayout.ConversionPattern = "%date %message%newline";
            patternLayout.ConversionPattern = "%message%newline";//取消log4的时间记录
            patternLayout.ActivateOptions();

            // configurating the RollingFileAppender object
            RollingFileAppender appender = new RollingFileAppender();
            if (name.Contains("Camera"))
                appender.Encoding = Encoding.UTF8;
            appender.Name = name;
            appender.AppendToFile = true;
            appender.File = name;//日志存放的目录结构，文件夹->文件名称
            appender.StaticLogFileName = true;
            appender.PreserveLogFileNameExtension = true;
            appender.CountDirection = 1;//修改超大小文件名称后缀方向Gets or sets the rolling file count direction
            // lockingModel影响性能。有三种模式：ExclusiveLock，MinimalLock，InterProcessLock。
            /*MinimalLock是最小锁，速度很慢。因为每次都打开关闭文件。程序打开时，Log可实时删除，证明确实最小化锁文件。
             * 默认是ExclusiveLock，此时若程序打开且第一次写Log后，Log文件就被锁定，Log文件无法删除。
             * InterProcessLock，进程间锁。速度不慢与ExclusiveLock相似。且提供进程间同步，内部使用了Mutex。
             */
            //appender.LockingModel = new FileAppender.MinimalLock();
            appender.LockingModel = new FileAppender.ExclusiveLock();
            appender.Layout = patternLayout;
            //<!--保留的log文件数量 超过此数量后 自动删除之前的  -->
            appender.MaxSizeRollBackups = 2048;//单文件10M，最大可以保存20G
            //<!--单个文件最大数量-->
            appender.MaximumFileSize = "10MB";
            appender.RollingStyle = RollingFileAppender.RollingMode.Composite;
            appender.ActivateOptions();
            //强制设置为UTF-8格式
            // appender.Encoding = Encoding.UTF8;
            Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();

            var loger = hierarchy.GetLogger(name, hierarchy.LoggerFactory);
            loger.Hierarchy = hierarchy;
            loger.AddAppender(appender);
            loger.Level = Level.All;
            /*log4j.additivity是 子Logger 是否继承 父Logger 的 输出源（appender） 的标志位。
            具体说，默认情况下 子Logger 会继承 父Logger 的appender，也就是说 子Logger 会在 父Logger 的appender里输出。
            若是additivity设为false，则 子Logger 只会在自己的appender里输出，而不会在 父Logger 的appen*/
            loger.Additivity = false;

            BasicConfigurator.Configure(appender);

            var appname = Assembly.GetEntryAssembly().GetName().Name;
            var version = Assembly.GetEntryAssembly().GetName().Version;
            loger.Log(Level.Info, $"Log name {name} created for Application: {appname} Version: {version}", null);
            #endregion
        }

        /// <summary>
        /// 回调异常
        /// </summary>
        /// <param name="msg"></param>
        private void HintEvent(string msg)
        {
            if (HintEventHandler != null) HintEventHandler(this, msg);
        }

        #endregion
    }
}
